package com.anb.net;

import com.anb.common.Log;
import com.anb.common.Utils;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Server extends NetObject {

    public Server(IRunner runner, Constructor<?> connectionConstructor, Object... connectionArgs) {
        super("*server*");
        this.runner = runner;
        this.connectionConstructor = connectionConstructor;
        this.connectionArgs = connectionArgs;
    }

    public Server(IRunner runner, Class<?> connectionClass, Object... connectionArgs) throws Exception {
        this(runner, Utils.matchConstructor(connectionClass, ConnectionBase.getArgTypes(connectionArgs)), connectionArgs);
    }

    private IRunner runner;

    private Constructor<?> connectionConstructor;
    private Object[] connectionArgs;

    private ServerSocketChannel channel;
    public ServerSocketChannel getChannel() {
        return channel;
    }

    public void start(InetAddress host, int port) throws IOException {
        setAlias(String.format("%s:%s", host.toString(), port));

        Log.Get().info(getAlias() + " starts");
        channel = ServerSocketChannel.open();
        channel.configureBlocking(false);
        InetSocketAddress isa = new InetSocketAddress(host, port);
        channel.socket().bind(isa);

        runner.register(this);
    }
    @Override
    public void stop() {
        super.stop();
        runner.unregister(this);
        try {
            channel.close();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " stop error", e);
        }
    }

    protected void accepted(SelectionKey key) {
        SocketChannel c = null;
        try {
            c = channel.accept();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " accept error", e); // ignore
            return;
        }

        String remote;
        try {
            remote = c.getRemoteAddress().toString();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " get remote error", e); // ignore
            remote = "?remote?";
        }
        Log.Get().info(getAlias() + " accepted " + remote);

        Object[] args = Utils.flat(runner, c, connectionArgs);
        ConnectionBase connection;
        try {
            connection = (ConnectionBase)connectionConstructor.newInstance(args);
        } catch (Exception e) {
            Log.Get().error(getAlias() + " create connection for " + remote + " error", e);
            try {
                c.close();
            } catch (IOException e1) {
                Log.Get().error(getAlias() + " close channel " + remote + " error", e);
            }
            return;
        }

        try {
            connection.start();
        } catch (IOException e) {
            Log.Get().error(getAlias() + " start connection for " + remote + " error", e);
            connection.stop();
        }
    }
}
